﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Shapes;
using Newtonsoft.Json;
using PropertyChanged;
using Spire.Pdf;
using Spire.Pdf.Bookmarks;
using Spire.Pdf.General;
using Path = System.IO.Path;

namespace SpirePdfTool
{
    public partial class WindowSpirePdf : Window, INotifyPropertyChanged
    {
        #region BindableBase

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
        {
            if (Equals(storage, value)) return false;

            storage = value;
            OnPropertyChanged(propertyName);
            return true;
        }

        #endregion

        private string _FileName = "AddBookmark.pdf";
        private PdfDocument _Pdf = new PdfDocument();

        private List<BookmarkItem> _ListBookmarks;
        public List<BookmarkItem> ListBookmarks
        {
            get => _ListBookmarks;
            set => SetProperty(ref _ListBookmarks, value);
        }

        private bool _CanAdd;
        public bool CanAdd
        {
            get => _CanAdd;
            set => SetProperty(ref _CanAdd, value);
        }

        private bool _CanModify;
        public bool CanModify
        {
            get => _CanModify;
            set => SetProperty(ref _CanModify, value);
        }

        private bool _IsAuto;
        public bool IsAuto
        {
            get => _IsAuto;
            set => SetProperty(ref _IsAuto, value);
        }

        public WindowSpirePdf()
        {
            InitializeComponent();
            DataContext = this;
        }

        #region 测试区

        //测试：《C# 添加、修改和删除 PDF 书签》
        //https://www.cnblogs.com/Yesi/p/7251214.html

        private void BtnAddBookmarks_Click(object sender, EventArgs e)
        {
            //新建PDF文档
            PdfDocument pdf = new PdfDocument();

            //添加页面
            PdfPageBase page = pdf.Pages.Add();

            //添加书签
            PdfBookmark bookmark = pdf.Bookmarks.Add("第一页");

            //设置书签所指向的页面和位置，（0,0）表示页面的开始位置
            //bookmark.Destination = new PdfDestination(page)
            //{
            //    Location = new PointF(0, 0)
            //};

            bookmark.Destination = new PdfDestination(0, new PointF(0, 0), 0);

            //设置书签的文本格式和颜色
            bookmark.DisplayStyle = PdfTextStyle.Bold;
            bookmark.Color = Color.Black;

            //再添加一页，供其它功能测试使用;
            pdf.Pages.Add();

            //保存文档
            pdf.SaveToFile(_FileName);

            MessageBox.Show($"已保存文件{_FileName}");
        }

        private void BtnAddSubBookmarks_Click(object sender, EventArgs e)
        {
            //新建PDF文档
            PdfDocument pdf = new PdfDocument();

            //添加页面
            PdfPageBase page = pdf.Pages.Add();

            //添加书签
            PdfBookmark bookmark = pdf.Bookmarks.Add("第一章 热传导");

            //设置书签指向的页面和位置
            bookmark.Destination = new PdfDestination(page) { Location = new PointF(0, 0) };

            //设置书签的文本格式和颜色
            bookmark.DisplayStyle = PdfTextStyle.Bold;
            bookmark.Color = Color.SeaGreen;

            //添加子书签
            PdfBookmark childBookmark = bookmark.Insert(0, "1.1 热传导基本知识");

            //设置子书签指向的页面和位置
            childBookmark.Destination = new PdfDestination(page) { Location = new PointF(400, 300) };

            //设置子书签的文本格式和颜色
            childBookmark.DisplayStyle = PdfTextStyle.Regular;
            childBookmark.Color = Color.Black;

            //保存文档
            string fileName = "ChildBookmark.pdf";
            pdf.SaveToFile(fileName);

            MessageBox.Show($"已保存文件{fileName}");
        }

        private void BtnAddToFile_Click(object sender, EventArgs e)
        {
            //加载文档
            PdfDocument pdf = new PdfDocument();
            pdf.LoadFromFile(_FileName);

            for (int i = 0; i < pdf.Pages.Count; i++)
            {
                //添加书签
                PdfBookmark bookmark = pdf.Bookmarks.Add($"第{i + 1}章");

                //设置书签指向的页面和位置
                bookmark.Destination = new PdfDestination(pdf.Pages[i]) { Location = new PointF(0, 0) };

                //设置书签的文本格式和颜色
                bookmark.DisplayStyle = PdfTextStyle.Bold;
                bookmark.Color = Color.Black;
            }

            //保存文档
            pdf.SaveToFile(_FileName);

            MessageBox.Show($"已保存文件{_FileName}");
        }

        private void BtnModify_Click(object sender, EventArgs e)
        {
            //加载文档
            PdfDocument pdf = new PdfDocument();
            pdf.LoadFromFile(_FileName);

            //获取书签列表
            PdfBookmarkCollection bookmarks = pdf.Bookmarks;

            //获取第一个书签
            PdfBookmark bookmark = bookmarks[0];

            //修改书签指向的页面
            bookmark.Destination = new PdfDestination(pdf.Pages[1]);

            //修改书签的文本格式和颜色
            bookmark.DisplayStyle = PdfTextStyle.Bold;
            bookmark.Color = Color.Green;

            //修改书签的title
            bookmark.Title = "修改";

            //保存文档
            string fileName = "ModifyBookmark.pdf";
            pdf.SaveToFile(fileName);

            MessageBox.Show($"已保存文件{fileName}");
        }

        private void BtnInsert_Click(object sender, EventArgs e)
        {
            //加载文档
            PdfDocument pdf = new PdfDocument();
            pdf.LoadFromFile(_FileName);

            //插入新书签到指定位置（此处插入的是第三个书签的位置）
            PdfBookmark bookmark = pdf.Bookmarks.Insert(2, "新增第三章");

            //设置书签所指向的页面和位置
            bookmark.Destination = new PdfDestination(pdf.Pages[1]);
            bookmark.Destination.Location = new PointF(0, 300);

            //保存文档
            string fileName = "InsertBookmark.pdf";
            pdf.SaveToFile(fileName);

            MessageBox.Show($"已保存文件{fileName}");
        }

        private void BtnDelete_Click(object sender, EventArgs e)
        {
            //加载文档
            PdfDocument pdf = new PdfDocument();
            pdf.LoadFromFile(_FileName);

            //获取书签列表
            PdfBookmarkCollection bookmarks = pdf.Bookmarks;

            //删除第一个书签
            bookmarks.RemoveAt(0);

            //保存文档
            string fileName = "DeleteBookmark.pdf";
            pdf.SaveToFile(fileName);

            MessageBox.Show($"已保存文件{fileName}");
        }

        /// <summary>
        /// 破解方法：移除第一页版权信息
        /// https://blog.csdn.net/weixin_38211198/article/details/90115310
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BtnCrack_Click(object sender, EventArgs e)
        {
            //新建PDF文档
            PdfDocument pdf = new PdfDocument();

            //添加页面
            pdf.Pages.Add();
            pdf.Pages.Add();
            pdf.Pages.RemoveAt(0);

            //保存文档
            string fileName = "Crack.pdf";
            pdf.SaveToFile(fileName);

            MessageBox.Show($"已保存文件{fileName}");
        }

        #endregion

        private async void BtnChooseFile_Click(object sender, EventArgs e)
        {
            Microsoft.Win32.OpenFileDialog dialog = new Microsoft.Win32.OpenFileDialog { Filter = "PDF文件|*.pdf" };
            if (dialog.ShowDialog() == true)
            {
                TbPath.Text = _FileName = dialog.FileName;
                //MessageBox.Show($"已选择文件{_FileName}");

                if (await LoadBookmarks())
                {
                    //MessageBox.Show("载入书签成功");
                }
                else
                {
                    MessageBox.Show("载入书签失败");
                }
            }
        }

        private async Task<bool> LoadBookmarks()
        {
            bool result = false;
            Cursor = Cursors.Wait;

            try
            {
                await Task.Run(() =>
                {
                    _Pdf.LoadFromFile(_FileName);

                    PdfBookmarkCollection bookmarks = _Pdf.Bookmarks;
                    if (bookmarks.Count == 0)
                    {
                        bookmarks.Add("null");
                        bookmarks[0].Destination = new PdfDestination(_Pdf.Pages[0]);
                    }

                    ListBookmarks = GetBookmarksList(bookmarks);

                });
                
                CanAdd = true;
                result = true;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }

            Cursor = null;
            return result;
        }

        private List<BookmarkItem> GetBookmarksList(PdfBookmarkCollection bookmarks, BookmarkItem parent = null)
        {
            List<BookmarkItem> list = new List<BookmarkItem>();
            for (int i = 0; i < bookmarks.Count; i++)
            {
                try
                {
                    var bookmark = bookmarks[i];
                    var page = bookmark.Destination.Page;
                    int num = _Pdf.Pages.IndexOf(page) + 1;

                    var item = new BookmarkItem()
                    {
                        ParentId = parent?.Id ?? -1,
                        Parent = parent,
                        Id = i,
                        Page = num,
                        Title = bookmark.Title
                    };

                    if (bookmark.Count > 0)
                    {
                        item.Children = GetBookmarksList(bookmark, item);
                    }

                    list.Add(item);
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"序号为{i}的书签载入失败：{ex}");
                }
            }

            return list;
        }

        private void TvBookmarks_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            var bookmarkItem = e.NewValue as BookmarkItem;
            (TvBookmarks.SelectedItem as BookmarkItem).IdStr = GetIdStr(bookmarkItem);

            CanModify = true;
        }

        private string GetIdStr(BookmarkItem bookmarkItem)
        {
            string result = bookmarkItem.Id.ToString();

            int parentId = bookmarkItem.ParentId;
            var parent = bookmarkItem.Parent;

            while (parentId != -1)
            {
                result = $"{parent.Id}-{result}";
                parent = parent.Parent;
                parentId = parent?.Id ?? -1;
            }

            return result;
        }

        private PdfBookmark GetPdfBookmark(string idStr)
        {
            PdfBookmarkCollection collection = _Pdf.Bookmarks;
            PdfBookmark pdfBookmark = null;

            try
            {
                int[] ids = idStr.Split('-').Select(int.Parse).ToArray();
                for (int i = 0; i < ids.Length; i++)
                {
                    if (ids[i] > collection.Count || ids[i] < 0)
                    {
                        throw new Exception($"第{i + 1}个数值[{ids[i]}]不符合规范!");
                    }

                    pdfBookmark = collection[ids[i]];
                    collection = pdfBookmark;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"{ex}");
            }

            return pdfBookmark;
        }

        private bool DeletePdfBookmark(string idStr)
        {
            PdfBookmarkCollection collection = _Pdf.Bookmarks;
            PdfBookmark pdfBookmark;
            bool result = false;

            try
            {
                int[] ids = idStr.Split('-').Select(int.Parse).ToArray();
                for (int i = 0; i < ids.Length - 1; i++)
                {
                    if (ids[i] > collection.Count || ids[i] < 0)
                    {
                        throw new Exception($"第{i + 1}个数值[{ids[i]}]不符合规范!");
                    }

                    pdfBookmark = collection[ids[i]];
                    collection = pdfBookmark;
                }

                collection.RemoveAt(ids[ids.Length - 1]);
                result = true;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"{ex}");
            }

            return result;
        }

        private bool AddPdfBookmark(string idStr)
        {
            PdfBookmarkCollection collection = _Pdf.Bookmarks;
            PdfBookmark pdfBookmark;
            bool result = false;
            var bookmarkItem = TvBookmarks.SelectedItem as BookmarkItem;

            try
            {
                int[] ids = idStr.Split('-').Select(int.Parse).ToArray();
                for (int i = 0; i < ids.Length - 1; i++)
                {
                    if (ids[i] > collection.Count || ids[i] < 0)
                    {
                        throw new Exception($"第{i + 1}个数值[{ids[i]}]不符合规范!");
                    }

                    pdfBookmark = collection[ids[i]];
                    collection = pdfBookmark;
                }

                PdfBookmark bookmark = null;
                int lastId = ids[ids.Length - 1];

                if (lastId < collection.Count)
                {
                    bookmark = collection.Insert(lastId, bookmarkItem?.Title ?? "");
                }
                else if (lastId >= collection.Count)
                {
                    bookmark = collection.Add(bookmarkItem?.Title ?? "");
                }

                if (bookmark != null)
                {
                    int pageIndex = bookmarkItem?.Page - 1 ?? 0;
                    if (pageIndex >= _Pdf.Pages.Count)
                    {
                        pageIndex = _Pdf.Pages.Count - 1;
                    }

                    bookmark.Destination = new PdfDestination(_Pdf.Pages[pageIndex]);
                }

                result = true;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"{ex}");
            }

            return result;
        }

        /// <summary>
        /// 添加书签（暂时供导入书签使用）
        /// </summary>
        /// <param name="collection"></param>
        /// <param name="items"></param>
        private async Task AddPdfBookmark(PdfBookmarkCollection collection, List<BookmarkItem> items)
        {
            await Task.Run(async () =>
            {
                foreach (var bookmarkItem in items)
                {
                    PdfBookmark bookmark = collection.Add(bookmarkItem?.Title ?? "");

                    if (bookmark != null)
                    {
                        int pageIndex = bookmarkItem?.Page - 1 ?? 0;
                        if (pageIndex >= _Pdf.Pages.Count)
                        {
                            pageIndex = _Pdf.Pages.Count - 1;
                        }

                        bookmark.Destination = new PdfDestination(_Pdf.Pages[pageIndex]);
                    }

                    if (bookmarkItem.Children.Any())
                    {
                        await AddPdfBookmark(collection[collection.Count - 1], bookmarkItem.Children);
                    }
                }
            });
        }

        private void BtnModify_OnClick(object sender, RoutedEventArgs e)
        {
            var bookmarkItem = TvBookmarks.SelectedItem as BookmarkItem;

            //if (bookmarkItem.Id < 0)
            //{
            //    bookmarkItem.Id = 0;
            //}
            //else if (bookmarkItem.Id >= _Pdf.Bookmarks.Count)
            //{
            //    bookmarkItem.Id = _Pdf.Bookmarks.Count - 1;
            //}

            //PdfBookmark bookmark = _Pdf.Bookmarks[bookmarkItem.Id];

            PdfBookmark bookmark = GetPdfBookmark(bookmarkItem.IdStr);

            int pageIndex = bookmarkItem.Page - 1;
            if (pageIndex >= _Pdf.Pages.Count)
            {
                pageIndex = _Pdf.Pages.Count - 1;
            }

            bookmark.Destination = new PdfDestination(_Pdf.Pages[pageIndex]);
            bookmark.Title = bookmarkItem.Title;

            AutoSave();
        }

        private void BtnDelete_OnClick(object sender, RoutedEventArgs e)
        {
            var bookmarkItem = TvBookmarks.SelectedItem as BookmarkItem;

            //if (bookmarkItem.Id < 0)
            //{
            //    bookmarkItem.Id = 0;
            //}
            //else if (bookmarkItem.Id >= _Pdf.Bookmarks.Count)
            //{
            //    bookmarkItem.Id = _Pdf.Bookmarks.Count - 1;
            //}

            //_Pdf.Bookmarks.RemoveAt(bookmarkItem.Id);

            if (DeletePdfBookmark(bookmarkItem.IdStr))
            {
                AutoSave();
            }
            else
            {
                MessageBox.Show($"删除失败");
            }
        }

        private void BtnAdd_OnClick(object sender, RoutedEventArgs e)
        {
            var bookmarkItem = TvBookmarks.SelectedItem as BookmarkItem;

            if (AddPdfBookmark(bookmarkItem.IdStr))
            {
                AutoSave();
            }
            else
            {
                MessageBox.Show($"添加失败");
            }
        }

        private async void BtnRefresh_OnClick(object sender = null, RoutedEventArgs e = null)
        {
            if (await LoadBookmarks())
            {
                MessageBox.Show("刷新成功");
            }
            else
            {
                MessageBox.Show("刷新失败");
            }
        }

        private void BtnWrite_OnClick(object sender = null, RoutedEventArgs e = null)
        {
            _Pdf.SaveToFile(_FileName);
            MessageBox.Show($"【{_FileName}】已保存");
        }

        private void AutoSave()
        {
            if (IsAuto)
            {
                BtnWrite_OnClick();
                BtnRefresh_OnClick();
            }
            else
            {
                MessageBox.Show($"操作成功，但还未保存到文件.");
            }
        }

        private void BtnAbout_OnClick(object sender, RoutedEventArgs e)
        {
            new WindowAbout().ShowDialog();
        }

        /// <summary>
        /// 导出书签
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BtnExport_Click(object sender, RoutedEventArgs e)
        {
            string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), $"{DateTime.Now:yyyyMMddHHmmss}.json");
            string content = JsonConvert.SerializeObject(ListBookmarks, new JsonSerializerSettings()
            {
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
                Formatting = Formatting.Indented,
            });

            File.WriteAllText(path, content);
            MessageBox.Show($"书签已导出到文件[{path}]");
        }

        /// <summary>
        /// 导入书签
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void BtnImport_Click(object sender, RoutedEventArgs e)
        {
            Microsoft.Win32.OpenFileDialog dialog = new Microsoft.Win32.OpenFileDialog();
            dialog.Filter = "文本文件|*.json";
            if (dialog.ShowDialog() == true)
            {
                Cursor = Cursors.Wait;
                string content = File.ReadAllText(dialog.FileName);
                ListBookmarks = JsonConvert.DeserializeObject<List<BookmarkItem>>(content);

                //先清空原书签
                _Pdf.Bookmarks.Clear();

                await AddPdfBookmark(_Pdf.Bookmarks, ListBookmarks);
                IsAuto = true;
                AutoSave();

                Cursor = null;
                MessageBox.Show($"已导入书签并写入文件");
            }
        }
    }

    [AddINotifyPropertyChangedInterface]
    public class BookmarkItem
    {
        public int ParentId { get; set; } = -1;
        public int Id { get; set; }
        public string IdStr { get; set; }
        public int Page { get; set; }
        public string Title { get; set; }

        public List<BookmarkItem> Children { get; set; } = new List<BookmarkItem>();

        public BookmarkItem Parent { get; set; }
    }
}
